package com.yytek.am.core.runtime;

import akka.actor.AbstractActor;
import akka.actor.ActorSelection;
import akka.actor.Props;
import com.alibaba.fastjson.JSON;
import com.yytek.am.constants.AkkaConstants;
import com.yytek.am.core.msg.*;
import com.yytek.am.core.msg.InputDataMessage.InputData;
import com.yytek.am.core.rule.EventRule;
import com.yytek.am.core.rule.EventRuleClear;
import com.yytek.am.core.rule.EventRuleGenerate;
import com.yytek.am.util.ExpressionBuilder;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 处理告警清除/生成逻辑 告警记录缓存(eventRecordCache)中只保留清除/生成其中一种状态
 */
public class EventRuleActor extends AbstractActor {

    public static final String SEARCH_INDEX = "searchIndex";
    public static final int ONE_SECONE = 1 * 1000;

    private EventRule eventRule;
    private EventRuleGenerate eventRuleGenerate;
    private EventRuleClear eventRuleClear;
    private Set<String> rlObjects;

    public EventRuleActor(Set<String> rlObjects) {
        this.rlObjects = rlObjects;
    }

    /**
     * 缓存设备的告警记录
     */
    Map<String, EventRecord> eventRecordCache = new HashMap<String, EventRecord>();

    public static Props props(Set<String> rlObjects) {
        return Props.create(EventRuleActor.class, () -> new EventRuleActor(rlObjects));
    }

    @Override
    public Receive createReceive() {
        //告警规则修改删除
        return receiveBuilder().match(EventRuleUpdateMessage.class, this::onEventRuleUpdateMessage)
                .match(EventRuleGenerateUpdateMessage.class, this::onEventRuleGenerateUpdateMessage)
                .match(EventRuleClearUpdateMessage.class, this::onEventRuleClearUpdateMessage)
                //告警源数据输入
                .match(InputDataMessage.class, this::onInputDataMessage).build();
    }

    /**
     * 更新告警定义
     * @param message
     */
    private void onEventRuleUpdateMessage(EventRuleUpdateMessage message) {
        if (message.type == AkkaConstants.EventRuleUpdateType.UPDATE) {
            this.eventRule = message.eventRule;
            this.rlObjects = message.eventRule.getRlObjects();
            eventRecordCache.clear();
        }
    }

    /**
     * 更新告警生成规则
     * @param message
     */
    private void onEventRuleGenerateUpdateMessage(EventRuleGenerateUpdateMessage message) {
        if (message.type == AkkaConstants.EventRuleUpdateType.UPDATE) {
            this.eventRuleGenerate = message.eventRuleGenerate;
        }
    }

    /**
     * 更新告警清除规则
     * @param message
     */
    private void onEventRuleClearUpdateMessage(EventRuleClearUpdateMessage message) {
        if (message.type == AkkaConstants.EventRuleUpdateType.UPDATE) {
            this.eventRuleClear = message.eventRuleClear;
        }
    }

    /**
     * 计算告警
     * @param message
     */
    private void onInputDataMessage(InputDataMessage message) {
        //如果消息检查为空,或者不是绑定设备
        if (!message.inputData.checkComplete() || !rlObjects.contains(message.inputData.getDeviceName())) {
            return;
        }
        boolean isClear = dealClear(message.inputData);
        //如果不符合清除告警,就是生成告警
        if (!isClear) {
            dealGenerate(message.inputData);
        }
    }

    /**
     * 计算告警清除
     * @param inputData
     * @return
     */
    private boolean dealClear(InputData inputData) {
        //TODO:getSearchIndex方法获取kpiNmae+设备id
        String recordKey = getSearchIndex(inputData, this.eventRule.getSearchIndex());
        // 检查是否满足告警清除规则
        boolean isClear = this.eventRuleClear != null
                && (Boolean) ExpressionBuilder.getDefault().calculate(inputData, eventRuleClear.getCalculateExpress());
        if (isClear) {
            EventRecord record = null;
            if (eventRecordCache.containsKey(recordKey)) {
                record = eventRecordCache.get(recordKey);
                if (record.getType().intValue() == AkkaConstants.EventType.ALARM_CLEAR) {
                    record.setAccCount(record.getAccCount() + 1);
                } else {
                    record.setAccCount(1);
                    record.setCreateTime(System.currentTimeMillis());
                    record.setType(AkkaConstants.EventType.ALARM_CLEAR);
                }
            } else {
                record = createEventRecord(inputData);
                record.setType(AkkaConstants.EventType.ALARM_CLEAR);
                eventRecordCache.put(recordKey, record);
            }
            record.setUpdateTime(System.currentTimeMillis());

            isClear = isClear && checkClearAlarmNumAndTime(record);
            // 通知告警清除
            if (isClear) {
                //输出数据到mqtt
                getIoActor().tell(new OutputDataMessage(JSON.toJSONString(record)), this.getSelf());
                //重置告警数据
                resetEventRecord(record);
            }

        }
        return isClear;
    }

    /**
     * 计算告警生成
     * @param inputData
     * @return
     */
    private boolean dealGenerate(InputDataMessage.InputData inputData) {
        String recordKey = getSearchIndex(inputData, this.eventRule.getSearchIndex());
        boolean isAlarmOn = this.eventRuleGenerate != null && (Boolean) ExpressionBuilder.getDefault()
                .calculate(inputData, eventRuleGenerate.getCalculateExpress());
        if (isAlarmOn) {
            EventRecord record = null;
            if (eventRecordCache.containsKey(recordKey)) {
                record = eventRecordCache.get(recordKey);
                if (record.getType() != AkkaConstants.EventType.ALARM_CLEAR) {
                    record.setAccCount(record.getAccCount() + 1);
                } else {
                    record.setAccCount(1);
                    record.setCreateTime(System.currentTimeMillis());
                }
            } else {
                record = createEventRecord(inputData);
                eventRecordCache.put(recordKey, record);
            }
            record.setType(AkkaConstants.EventType.ALARM_GENERATE);
            record.setUpdateTime(System.currentTimeMillis());
            isAlarmOn = isAlarmOn && checkAlarmNumAndTime(record);
            if (isAlarmOn) {
                refreshEventRecord(inputData, record);
                // 通知告警生成
                getIoActor().tell(new OutputDataMessage(JSON.toJSONString(record)), this.getSelf());
                resetEventRecord(record);
            }
        }

        return isAlarmOn;
    }

    /**
     * 检查告警生成次数和时间压制
     * @param record
     * @return
     */
    private boolean checkAlarmNumAndTime(EventRecord record) {
        boolean flag = false;

        if (eventRuleGenerate.getAccCount() != null && 0 != eventRuleGenerate.getAccCount()) {
            if (record.getAccCount() >= eventRuleGenerate.getAccCount()) {
                flag = true;
            }
        } else if (eventRuleGenerate.getAccTime() != null && 0 != eventRuleGenerate.getAccTime()) {
            if ((record.getUpdateTime() - record.getCreateTime()) >= eventRuleGenerate.getAccTime() * ONE_SECONE) {
                flag = true;
            }
        } else {
            flag = true;
        }
        return flag;
    }

    /**
     * 检查告警清除次数和时间压制
     * @param record
     * @return
     */
    private boolean checkClearAlarmNumAndTime(EventRecord record) {
        boolean flag = false;
        if (eventRuleClear == null) {
            return flag;
        }
        if (eventRuleClear.getAccCount() != null && 0 != eventRuleClear.getAccCount()) {
            if (record.getAccCount() >= eventRuleClear.getAccCount()) {
                flag = true;
            }
        } else if (eventRuleClear.getAccTime() != null && 0 != eventRuleClear.getAccTime()) {
            if ((record.getUpdateTime() - record.getCreateTime()) >= eventRuleClear.getAccTime() * ONE_SECONE) {
                flag = true;
            }
        } else {
            flag = true;
        }
        return flag;
    }

    /**
     * 获取查询设备表达式
     * @param inputData
     * @param searchIndexExpress
     * @return
     */
    private String getSearchIndex(InputData inputData, String searchIndexExpress) {
        String searchIndex = null;
        if (inputData.containsKey(SEARCH_INDEX)) {
            searchIndex = inputData.getString(SEARCH_INDEX);
        } else {
            searchIndex = ExpressionBuilder.getDefault().calculate(inputData, searchIndexExpress).toString();
            inputData.put(SEARCH_INDEX, searchIndex);
        }
        return searchIndex;
    }

    private EventRecord createEventRecord(InputData inputData) {
        EventRecord record = new EventRecord();
        record.setAlarmDefineId(eventRule.getId());
        record.setCreateTime(System.currentTimeMillis());
        record.setKpiName(eventRule.getKpiName());
        record.setKpiValue(inputData.getKpiValue());
        record.setSearchIndex(getSearchIndex(inputData, this.eventRule.getSearchIndex()));
        record.setTitle(eventRule.getTitle());
        record.setAccCount(1);
        record.setLevel(eventRule.getLevel());
        record.setLabel(eventRule.getLabel());
        return record;
    }

    private void refreshEventRecord(InputData inputData, EventRecord record) {
        record.setContent(ExpressionBuilder.getDefault().calculate(inputData, eventRule.getContent()).toString());
        record.setKpiValue(inputData.getKpiValue());
        record.setJsonData(JSON.toJSONString(inputData));
    }

    private void resetEventRecord(EventRecord record) {
        record.setCreateTime(System.currentTimeMillis());
        record.setAccCount(0);
    }

    private ActorSelection getIoActor() {
        return this.getContext().getSystem()
                .actorSelection(AkkaConstants.Actor.ROOT_PATH + AkkaConstants.Actor.SEPARATOR + EventSourceSinkActor.class.getName());
    }

    /**
     * 告警记录缓存类
     */
    public class EventRecord implements Serializable {
        private static final long serialVersionUID = 1L;

        private Long alarmDefineId;
        private String label;
        private Integer level;
        private String title;
        private String content;
        private Integer accCount;
        private String kpiName;
        private String kpiValue;
        private String searchIndex;
        private String subIndex;
        private String jsonData;
        private Long createTime;
        private Long updateTime;
        /**
         * 消息事件类型 告警产生 告警清除(自动)
         */
        private Integer type;

        public Long getAlarmDefineId() {
            return alarmDefineId;
        }

        public void setAlarmDefineId(Long alarmDefineId) {
            this.alarmDefineId = alarmDefineId;
        }

        public String getLabel() {
            return label;
        }

        public void setLabel(String label) {
            this.label = label;
        }

        public Integer getLevel() {
            return level;
        }

        public void setLevel(Integer level) {
            this.level = level;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }

        public Integer getAccCount() {
            return accCount;
        }

        public void setAccCount(Integer accCount) {
            this.accCount = accCount;
        }

        public String getKpiName() {
            return kpiName;
        }

        public void setKpiName(String kpiName) {
            this.kpiName = kpiName;
        }

        public String getKpiValue() {
            return kpiValue;
        }

        public void setKpiValue(String kpiValue) {
            this.kpiValue = kpiValue;
        }

        public String getSearchIndex() {
            return searchIndex;
        }

        public void setSearchIndex(String searchIndex) {
            this.searchIndex = searchIndex;
        }

        public String getSubIndex() {
            return subIndex;
        }

        public void setSubIndex(String subIndex) {
            this.subIndex = subIndex;
        }

        public String getJsonData() {
            return jsonData;
        }

        public void setJsonData(String jsonData) {
            this.jsonData = jsonData;
        }

        public Long getCreateTime() {
            return createTime;
        }

        public void setCreateTime(Long createTime) {
            this.createTime = createTime;
        }

        public Long getUpdateTime() {
            return updateTime;
        }

        public void setUpdateTime(Long updateTime) {
            this.updateTime = updateTime;
        }

        public Integer getType() {
            return type;
        }

        public void setType(Integer type) {
            this.type = type;
        }
    }

}
